'use strict';

/**
 * 考勤逻辑控制器
 */
const Code = require('../common/error_code');
const Cons = require('../common/constants');
const Util = require('../utils/hobot_util');
const recordModel = require('../models/record_user.model');
const userModel = require('../models/user.model');
const attendanceModel = require('../models/attendance.model');
const configModel = require('../models/config.model');
const Config = require('../controllers/config');
const moment = require("moment");
const schedule = require("node-schedule");

/**
 * 考勤数据初加工
 */
async function init() {
    // 每天凌晨1、2、3点预处理前一天的数据 TODO
    schedule.scheduleJob('0 0 1 * * *', function () {
        // const date = new Date(2017, 10, 16, 17, 22, 0);
        // schedule.scheduleJob(date, function () {
        pretreat_data(moment().subtract(1, 'days').format('YYYYMMDD'));
    });
}

/**
 * 超级管理员重置全员的考勤
 * @param ctx
 * @returns {Promise.<void>}
 */
async function reset(ctx) {
    const params = ctx.request.body;
    // 校验参数
    if (!params.ad_date && !params.ad_month) {
        return ctx.error(Code.RET_ATTENDANCE_RESET_ERR_PARAM);
    }
    if (params.ad_date && (!Util.isDate(params.ad_date, 'YYYYMMDD'))) {
        return ctx.error(Code.RET_ATTENDANCE_RESET_ERR_PARAM);
    }
    if (params.ad_month && (!Util.isDate(params.ad_month, 'YYYYMM'))) {
        return ctx.error(Code.RET_ATTENDANCE_RESET_ERR_PARAM);
    }

    let rst = 0;
    // 指定某一天
    if (params.ad_date) {
        // 不能重置未来某日的考勤统计
        const currentDay = moment().format('YYYYMMDD');
        if (params.ad_date > currentDay) {
            return ctx.error(Code.RET_ATTENDANCE_RESET_ERR_PARAM_MONTH);
        }
        rst = await pretreat_data(params.ad_date);
    } else if (params.ad_month) {
        const currentMonth = moment().format('YYYYMM');
        // 不能重置未来某月的考勤统计
        if (params.ad_month > currentMonth) {
            return ctx.error(Code.RET_ATTENDANCE_RESET_ERR_PARAM_MONTH);
        }

        const month = params.ad_month;
        let days = moment(params.ad_month, 'YYYYMM').daysInMonth();
        // 当月只能重置到当天
        if (params.ad_month > currentMonth) {
            days = Number(moment().format('D'));
        }
        for (let d = 1; d <= days; d++) {
            rst = await pretreat_data(month + p(d));
            if (rst !== 0) {
                break;
            }
        }
    }

    if (rst === 0) {
        ctx.success(Code.RET_ATTENDANCE_RESET_OK);
    } else {
        ctx.error(Code.RET_ATTENDANCE_RESET_ERR_SERVER);
    }
}

/**
 * 从记录表整理出当天考勤时间
 * @param date
 * @returns {Promise.<number>}
 */
async function pretreat_data(date) {
    if (!Util.isDate(date, 'YYYYMMDD')) {
        return 1;
    }
    try {
        const mm = Util.fmtDate(date, 'YYYYMM');
        const d = Number(Util.fmtDate(date, 'D'));

        // 查找所有员工
        const users = await userModel.find({ $or: [{ u_tp: Cons.USER_TYPE_STAFF }, { u_tp: Cons.USER_TYPE_TRAINEE }] }, { u_hp: 0 });
        for (let i = 0; i < users.length; i++) {
            const user = users[i];

            const atCdt = { a_mm: mm, u_id: user._id };
            // 先占坑，把考勤月的记录生成
            await attendanceModel.update(atCdt, { a_mm: mm }, { upsert: true });

            // 先看看指定日期有没有记录，然后再找出最早时间和最晚时间插入考勤表
            let count = 0;
            let from = null;
            let to = null;
            const cdt = {
                u_id: user._id, r_at: {
                    $gte: new Date(moment(date)),
                    $lt: new Date(moment(date).add(1, 'days'))
                }
            };
            const opt = { b_qp: 0, r_cp: 0 };
            const tmp = await recordModel.aggregate({ $match: cdt }, { $project: opt },
                { $group: { _id: null, total: { $sum: 1 }, from: { $min: "$r_at" }, to: { $max: "$r_at" } } });
            if (tmp && tmp.length > 0) {
                count = tmp[0].total;
                from = tmp[0].from;
                to = tmp[0].to;
            }

            // 统一考勤统计逻辑
            const atInfo = await getAttendanceType(date, count, from, to);

            // 先清空当天数据、简单粗暴
            await attendanceModel.update(atCdt, { $pull: { a_dd: { date: d } } }, { multi: true });
            const atUpd = {
                $push: {
                    a_dd: {
                        $each: [{ date: d, from: atInfo.from, to: atInfo.to, flag: atInfo.type }],
                        $sort: { date: 1 }
                    }
                }
            };
            await attendanceModel.update(atCdt, atUpd);
        }
        return 0;
    } catch (err) {
        return 1;
    }
}

/**
 * 查询考勤统计（考虑到统计耗时，目前必须以部门为单位）
 */
/**
 * 应产品需求，现支持所有部门一起查询
 * @param ctx
 * @returns {Promise<void>}
 */
async function query_stat(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.ad_dept && !params.ad_month || (!Util.isDate(params.ad_month, 'YYYYMM'))) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_STAT_ERR_PARAM);
    }
    const currentMonth = moment().format('YYYYMM');
    // 统计数据只能查看本月以前的
    if (params.ad_month > currentMonth) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_STAT_ERR_MONTH);
    }

    let users;
    console.log(params)
    if (params.ad_dept == 'all') {
        //所有部门
        users = await userModel.find({}, { u_hp: 0 }).sort({ u_tp: 1, u_no: 1 });
    } else {
        // 校验部门是否存在
        const config = await configModel.findOne({ _id: Cons.CONFIG_TYPE_DEPT }, { c_va: 1 });
        if (!config) {
            return ctx.error(Code.RET_ATTENDANCE_QUERY_STAT_ERR_CONFIG);
        }
        const has = await config.c_va.some((item) => {
            return item.value == params.ad_dept
        });
        if (!has) {
            return ctx.error(Code.RET_ATTENDANCE_QUERY_STAT_ERR_DEPT);
        }
        // 查找指定部门下面的所有员工
        users = await userModel.find({ u_dm: params.ad_dept }, { u_hp: 0 }).sort({ u_tp: 1, u_no: 1 });
    }

    let rst = [];
    const depts = await Config.getDeptList();
    for (let i = 0; i < users.length; i++) {
        const user = users[i];

        let normalCnt = 0; //正常天数
        let earlyCnt = 0; //早退天数
        let lateCnt = 0; //迟到天数
        let absenteeismCnt = 0; //缺勤天数
        let abnormalcnt = 0; //异常天数，只有1条记录

        // 获取用户的指定月份考勤数据进行分析
        const atData = await attendanceModel.findOne({ u_id: user._id, a_mm: params.ad_month });
        if (atData && atData.a_dd) {
            atData.a_dd.forEach(function (item, index) {
                const date = moment(atData.a_mm + p(item.date));
                // 周六周日不参加统计
                const week = date.format('d');
                if (week != 0 && week != 6) {
                    // 根据type进行统计累加
                    if (item.flag == Cons.AD_TYPE_LATE) {
                        lateCnt += 1;
                    } else if (item.flag == Cons.AD_TYPE_EARLY) {
                        earlyCnt += 1;
                    } else if (item.flag == Cons.AD_TYPE_BAD) {
                        lateCnt += 1;
                        earlyCnt += 1;
                    } else if (item.flag == Cons.AD_TYPE_ABSENTEEISM) {
                        absenteeismCnt += 1;
                    } else if (item.flag == Cons.AD_TYPE_ABNORMAL) {
                        abnormalcnt += 1;
                    } else {
                        normalCnt += 1;
                    }
                }
            });
        }
        rst.push({
            user_id: user._id,
            user_name: user.u_nm,
            user_dept: Config.getConfigLabel(depts, user.u_dm),
            normal_count: normalCnt,
            late_count: lateCnt,
            early_count: earlyCnt,
            absenteeism_count: absenteeismCnt,
            abnormal_count: abnormalcnt
        });
    }

    ctx.success(Code.RET_ATTENDANCE_QUERY_STAT_OK, rst);
}

/**
 * @deprecated 查询考勤统计（考虑到统计耗时，目前必须以部门为单位）
 * 实时查询，效率有点慢，
 */
async function query_stat_old(ctx) {
    const params = ctx.request.query;
    // 校验参数
    if (!params.ad_dept || !params.ad_month || (!Util.isDate(params.ad_month, 'YYYYMM'))) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_STAT_ERR_PARAM);
    }
    const currentMonth = moment().format('YYYYMM');
    // 统计数据只能查看本月以前的
    if (params.ad_month > currentMonth) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_STAT_ERR_PARAM);
    }

    // 查找指定部门下面的所有员工
    const users = await userModel.find({ u_dm: params.ad_dept }, { u_hp: 0 }).sort({ u_tp: 1, u_no: 1 });

    let rst = [];
    const depts = await Config.getDeptList();
    for (let i = 0; i < users.length; i++) {
        const user = users[i];

        let normalCnt = 0; //正常天数
        let earlyCnt = 0; //早退天数
        let lateCnt = 0; //迟到天数
        let absenteeismCnt = 0; //缺勤天数
        const currentMonthDays = moment(params.ad_month, 'YYYYMM').daysInMonth();
        // 循环指定月份的每一天
        for (let d = 1; d <= currentMonthDays; d++) {
            const date = moment(params.ad_month + p(d));
            const cdt = {
                u_id: user._id, r_at: {
                    $gte: new Date(date),
                    $lt: new Date(date.add(1, 'days')) //BUG
                }
            };
            const opt = { b_qp: 0, r_cp: 0 };

            // 先看看指定日期有没有记录，然后再找出最早时间和最晚时间进行分析
            // const count = await recordModel.find(cdt, opt).count();
            let count = 0;
            let from = '';
            let to = '';
            const tmp = await recordModel.aggregate({ $match: cdt }, { $project: opt },
                { $group: { _id: null, total: { $sum: 1 }, from: { $min: "$r_at" }, to: { $max: "$r_at" } } });
            if (tmp && tmp.length > 0) {
                count = tmp[0].total;
                from = tmp[0].from;
                to = tmp[0].to;
            }

            let type = Cons.AD_TYPE_NORMAL;
            // 周六周日不参加统计
            const week = date.format('d');
            if (week == 0 || week == 6) {
                continue;
            }
            // 没有记录
            if (count === 0) {
                type = Cons.AD_TYPE_ABSENTEEISM;
            } else if (count === 1) {
                // const d = (await recordModel.findOne(cdt, opt)).r_at;
                const h = Util.fmtDate(from, 'H');
                // 1条记录，时间点在上午算早退，时间点在下午算迟到
                if (h < Cons.ATTENDANCE_DISTINGUISH_TIME) {
                    type = Cons.AD_TYPE_EARLY;
                } else {
                    type = Cons.AD_TYPE_LATE;
                }
            } else {
                // const from = (await recordModel.find(cdt, opt).sort({r_at: 1}).limit(1))[0].r_at;
                // const to = (await recordModel.find(cdt, opt).sort({r_at: -1}).limit(1))[0].r_at;

                // 分析一下时间段，迟到，早退，请假
                const f = Util.fmtDate(from, 'H');
                const t = Util.fmtDate(to, 'H');
                // 半天，算缺勤
                if (f >= Cons.ATTENDANCE_DISTINGUISH_TIME || t < Cons.ATTENDANCE_DISTINGUISH_TIME) {
                    type = Cons.AD_TYPE_ABSENTEEISM;
                }
            }

            // 根据type进行统计累加
            if (type == Cons.AD_TYPE_LATE) {
                lateCnt += 1;
            } else if (type == Cons.AD_TYPE_EARLY) {
                earlyCnt += 1;
            } else if (type == Cons.AD_TYPE_ABSENTEEISM) {
                absenteeismCnt += 1;
            } else {
                normalCnt += 1;
            }
        }
        rst.push({
            user_id: user._id,
            user_name: user.u_nm,
            user_dept: Config.getConfigLabel(depts, user.u_dm),
            normal_count: normalCnt,
            late_count: lateCnt,
            early_count: earlyCnt,
            absenteeism_count: absenteeismCnt
        });
    }

    ctx.success(Code.RET_ATTENDANCE_QUERY_STAT_OK, rst);
}

/**
 * 查询个人详细
 */
async function query_detail(ctx) {
    const params = ctx.request.query;

    // 校验参数
    if (!params.user_id || !params.ad_month || !Util.isDate(params.ad_month, 'YYYYMM')) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_DETAIL_ERR_PARAM);
    }

    const currentMonth = moment().format('YYYYMM');
    // 统计数据只能查看本月以前的
    if (params.ad_month > currentMonth) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_DETAIL_ERR_MONTH);
    }
    const has = await userModel.findOne({ _id: params.user_id })
    if (!has) {
        return ctx.error(Code.RET_ATTENDANCE_QUERY_DETAIL_ERR_USER)
    }
    // 当天数据现算，历史数据都是查询考勤表
    const atData = await attendanceModel.findOne({ u_id: params.user_id, a_mm: params.ad_month });
    let rst = [];
    const daysInMonth = moment(params.ad_month, 'YYYYMM').daysInMonth();
    // 循环指定月份的每一天
    for (let d = 1; d <= daysInMonth; d++) {
        const date = params.ad_month + p(d);
        let isTimeShow = Cons.AD_TIME_STATUS_ON;
        // 暂定周六周日不显示
        const week = Util.fmtDate(date, 'd');
        if (week == 0 || week == 6) {
            isTimeShow = Cons.AD_TIME_STATUS_OFF;
        }

        // 未来的情况，置空
        if (date > Util.fmtDate(moment(), 'YYYYMMDD')) {
            break;
        }
        // 当天的情况
        else if (date == Util.fmtDate(moment(), 'YYYYMMDD')) {
            let count = 0;
            let from = null;
            let to = null;
            const cdt = {
                u_id: params.user_id, r_at: {
                    $gte: new Date(moment(date)),
                    $lt: new Date(moment(date).add(1, 'days'))
                }
            };
            const opt = {
                b_qp: 0,
                r_cp: 0
            };
            const tmp = await recordModel.aggregate({ $match: cdt }, { $project: opt },
                { $group: { _id: null, total: { $sum: 1 }, from: { $min: "$r_at" }, to: { $max: "$r_at" } } });
            if (tmp && tmp.length > 0) {
                count = tmp[0].total;
                from = tmp[0].from;
                to = tmp[0].to;
            }

            // 暂定周六周日不显示
            let isTimeShow = Cons.AD_TIME_STATUS_ON;
            const week = moment().format('d');
            if (week == 0 || week == 6) {
                isTimeShow = Cons.AD_TIME_STATUS_OFF;
            }

            // 统一考勤统计逻辑
            const atInfo = await getAttendanceType(Util.fmtDate(moment(), 'YYYYMMDD'), count, from, to);

            rst.push({
                date: d,
                type: atInfo.type,
                isTimeShow: isTimeShow,
                from: atInfo.from ? Util.fmtDate(atInfo.from, 'HH:mm') : '',
                to: atInfo.to ? Util.fmtDate(atInfo.to, 'HH:mm') : ''
            });
        } else {
            let hasRec = false;
            if (atData && atData.a_dd && atData.a_dd.length > 0) {
                for (let i = 0; i < atData.a_dd.length; i++) {
                    const item = atData.a_dd[i];
                    if (date == atData.a_mm + p(item.date)) {
                        hasRec = true;
                        rst.push({
                            date: item.date,
                            type: item.flag,
                            isTimeShow: isTimeShow,
                            from: item.from ? Util.fmtDate(item.from, 'HH:mm') : '',
                            to: item.to ? Util.fmtDate(item.to, 'HH:mm') : ''
                        });
                        break;
                    }
                }
            }

            // 找不到记录，置空
            if (!hasRec) {
                rst.push({
                    date: d,
                    type: Cons.AD_TYPE_ABSENTEEISM,
                    isTimeShow: isTimeShow
                });
            }
        }
    }

    ctx.success(Code.RET_ATTENDANCE_QUERY_DETAIL_OK, rst);
}

/**
 * 补0函数
 * @param s
 * @returns {string}
 */
function p(s) {
    return s < 10 ? '0' + s : s;
}

/**
 * 考勤类型逻辑
 */
async function getAttendanceType(date, count, from, to) {
    let type = Cons.AD_TYPE_NORMAL;
    // 周六周日不参加统计
    const week = Util.fmtDate(date, 'd');
    if (week == 0 || week == 6) {
        return { from: from, to: to, type: type };
    }

    // 没有记录
    if (count === 0) {
        // 全天抓拍为0判断系统时间，如果早于系统当前日期的前一天显示为缺勤，晚于系统当天时间（含当天）不显示
        if (date !== Util.fmtDate(moment(), 'YYYYMMDD')) {
            type = Cons.AD_TYPE_ABSENTEEISM;
        }
    } else if (count === 1) {
        // 只有1条记录的情况，显示为异常（因为有可能是上班没抓到，也可能是下班没抓到
        // 这两个属性无法确定，所以不在系统中硬性规定是算迟到还是早退）
        type = Cons.AD_TYPE_ABNORMAL;
    } else {
        // 迟到-全天抓拍按时间排序，第一个抓拍的时间晚于考勤设置中上班时间+晚于时间的显示迟到
        // 早退-全天抓拍按时间排序，最后一个抓拍的时间早于考勤设置中下班时间+早于时间的显示早退（两种状态可能同时存在于日历中）

        // 分析一下上下班时间
        let workOn = Cons.WORK_ON_TIME;
        let workOff = Cons.WORK_OFF_TIME;
        let firstActual = Util.fmtDate(from, 'HHmm');
        let lastActual = Util.fmtDate(to, 'HHmm');
        const atCfg = await Config.getAttendanceConfig();
        if (atCfg && atCfg.work_on && atCfg.work_on_limit && atCfg.work_off && atCfg.work_off_limit) {
            workOn = atCfg.work_on;
            workOff = atCfg.work_off;
            firstActual = Util.fmtDate(moment(from).subtract(Number(atCfg.work_on_limit), 'minutes'), 'HHmm');
            lastActual = Util.fmtDate(moment(to).add(Number(atCfg.work_off_limit), 'minutes'), 'HHmm');
        }
        if (firstActual > workOn) {
            type = Cons.AD_TYPE_LATE;
        }
        if (lastActual < workOff) {
            if (type === Cons.AD_TYPE_LATE) {
                type = Cons.AD_TYPE_BAD;
            } else {
                type = Cons.AD_TYPE_EARLY;
            }
        }
    }

    return { from: from, to: to, type: type };
}

async function clean(ctx) {
    //
    await attendanceModel.remove();
    return ctx.success(Code.RET_ATTENDANCE_CLEAN_OK);
}

// 考勤数据初加工
exports.init = init;
// 查询考勤统计（考虑到统计耗时，目前必须以部门为单位）
exports.query_stat = query_stat;
exports.query_stat_old = query_stat_old;
// 查询个人详细
exports.query_detail = query_detail;
// 超级管理员重置某一天全员的考勤
exports.reset = reset;

exports.clean = clean;
